﻿using System;
using System.Collections.Generic;
using System.Text;
using KSS.Generator.Configuration;
using System.IO;
using KSS.Generator.Schema;

namespace KSS.Generator
{
    public class SqlProjectGenerator : ProjectGenerator
    {
        public override string ProjectName
        {
            get { return Template.Project.SqlProjectName; }
        }

        /// <summary>
        /// Generate all special template file
        /// </summary>
        /// <param name="projectConfig"></param>
        /// <param name="templateFile"></param>
        /// <param name="generatedDirectory"></param>
        protected override void GenerateFile(ProjectSetting projectConfig,
                                FileInfo templateFile,
                                DirectoryInfo generatedDirectory)
        {
            switch (templateFile.Name)
            {
                case Template.SqlProject.GeneratedProviderFileName:
                    GenerateProviderFile(projectConfig, templateFile, generatedDirectory);
                    break;

                case Template.SqlProject.CustomProviderFileName:
                    GenerateCustomProviderFile(projectConfig, templateFile, generatedDirectory);
                    break;

                case Template.SqlProject.GeneratedStoredProcProviderFileName:
                    GenerateStoredProcedureFile(projectConfig, templateFile, generatedDirectory);
                    break;

                case Template.SqlProject.CustomStoredProcProviderFileName:
                    GenerateCustomStoredProcedureFile(projectConfig, templateFile, generatedDirectory);
                    break;
            }
        }

        private void GenerateProviderFile(ProjectSetting projectConfig,
                                FileInfo templateFile,
                                DirectoryInfo generatedDirectory)
        {
            // Get entity mapper template file content
            string templateContent = GeneratorHelper.ReadFromFile(templateFile.FullName);

            // Mapper file name template
            string fileNameFormat = GeneratorHelper.GetFileNameFromat(templateFile.Name);

            string sourceCode = string.Empty;

            // Fill methods to template
            foreach (Table t in Context.DatabaseSchema.Tables)
            {
                StringBuilder sbEntityMethods = new StringBuilder();
                StringBuilder sbAddCommands = new StringBuilder();
                StringBuilder sbParamCommands = new StringBuilder();

                List<Column> primaryColumns = t.DataType.GetPrimaryColumns();
                List<Column> foreignColumns = t.DataType.GetForeignColumns();
                List<Association> foreignAssociations = t.DataType.GetForeignAssociations();

                #region { Parameters }

                foreach (Column c in t.DataType.Columns)
                {
                    sbAddCommands.AppendLine(
                        string.Format(Template.SqlProject.CommandTemplate_Add,
                                        t.Member,
                                        c.Name));

                    sbParamCommands.AppendFormat(
                        Template.SqlProject.CommandTemplate_TableAddInParam,
                        t.Member,
                        c.Name,
                        GeneratorHelper.GenerateLocalVariableName(t.DataType.Name));
                }
                #endregion

                #region { Get count }

                sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetCount,
                                        t.DataType.Name,
                                        t.Member);

                sbEntityMethods.AppendLine(sourceCode);
                #endregion

                #region { GetByPrimaryKey }
                foreach (Column c in primaryColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetByPrimaryKey,
                                        t.DataType.Name,
                                        c.Name,
                                        GeneratorHelper.GetTypeFromColumn(c),
                                        GeneratorHelper.GenerateLocalVariableName(c.Name),
                                        t.Member,
                                        Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                    sbEntityMethods.AppendLine(sourceCode);
                }

                #endregion

                #region { GetByForeignKey }

                foreach (Column c in foreignColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetByForeignKey,
                                       t.DataType.Name,
                                       c.Name,
                                       GeneratorHelper.GetTypeFromColumn(c),
                                       GeneratorHelper.GenerateLocalVariableName(c.Name),
                                       t.Member,
                                       Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                    sbEntityMethods.AppendLine(sourceCode);
                }

                #endregion

                #region { GetByForeignKeyEntity }

                foreach (Association a in foreignAssociations)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetByForeignKeyEntity,
                                       t.DataType.Name,
                                       a.ThisKey,
                                       a.Member,
                                       GeneratorHelper.GenerateLocalVariableName(a.Member));

                    sbEntityMethods.AppendLine(sourceCode);
                }

                #endregion

                #region { GetByForeignKeyWithPaging }

                foreach (Column c in foreignColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetByForeignKeyWithPaging,
                                       t.DataType.Name,
                                       c.Name,
                                       GeneratorHelper.GetTypeFromColumn(c),
                                       GeneratorHelper.GenerateLocalVariableName(c.Name),
                                       t.Member,
                                       primaryColumns[0].Name,
                                       Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                    sbEntityMethods.AppendLine(sourceCode);
                }

                #endregion

                #region { GetByForeignKeyEntityWithPaging }

                foreach (Association a in foreignAssociations)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetByForeignKeyEntityWithPaging,
                                       t.DataType.Name,
                                       a.Member,
                                       a.ThisKey,
                                       GeneratorHelper.GenerateLocalVariableName(a.Member),
                                       t.Member,
                                       primaryColumns[0].Name,
                                       Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                    sbEntityMethods.AppendLine(sourceCode);
                }

                #endregion

                #region { GetAll }

                sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetAll,
                                     t.DataType.Name,
                                     t.Member,
                                     Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                sbEntityMethods.AppendLine(sourceCode);

                #endregion

                #region { GetAllWithPaging }

                sourceCode = string.Format(Template.SqlProject.FunctionTemplate_GetAllWithPaging,
                                    t.DataType.Name,
                                    t.Member,
                                    primaryColumns[0].Name,
                                    Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                sbEntityMethods.AppendLine(sourceCode);
                #endregion

                #region { Add }

                sourceCode = string.Format(Template.SqlProject.FunctionTemplate_Add,
                                    t.DataType.Name,
                                    GeneratorHelper.GenerateLocalVariableName(t.DataType.Name),
                                    t.Member,
                                    sbAddCommands.ToString(),
                                    sbParamCommands.ToString(),
                                    Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                sbEntityMethods.AppendLine(sourceCode);
                #endregion

                #region { Update }

                sourceCode = string.Format(Template.SqlProject.FunctionTemplate_Update,
                                    t.DataType.Name,
                                    GeneratorHelper.GenerateLocalVariableName(t.DataType.Name),
                                    t.Member,
                                    sbAddCommands.ToString(),
                                    sbParamCommands.ToString(),
                                    Template.SqlProject.CommandTemplate_ExecuteReturnTable);

                sbEntityMethods.AppendLine(sourceCode);
                #endregion

                #region { DeleteByPrimaryKey }

                foreach (Column c in primaryColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_DeleteByPrimaryKey,
                                   t.DataType.Name,
                                   c.Name,
                                   GeneratorHelper.GetTypeFromColumn(c),
                                   GeneratorHelper.GenerateLocalVariableName(c.Name),
                                   t.Member);

                    sbEntityMethods.AppendLine(sourceCode);
                }
                #endregion

                #region { DeleteByPrimaryEntity }

                foreach (Column c in primaryColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_DeleteByEntity,
                                   t.DataType.Name,
                                   c.Name,
                                   GeneratorHelper.GenerateLocalVariableName(t.DataType.Name));

                    sbEntityMethods.AppendLine(sourceCode);
                }
                #endregion

                #region { DeleteByForeignKey }

                foreach (Column c in foreignColumns)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_DeleteByForeignKey,
                                   t.DataType.Name,
                                   c.Name,
                                   GeneratorHelper.GetTypeFromColumn(c),
                                   GeneratorHelper.GenerateLocalVariableName(c.Name),
                                   t.Member);

                    sbEntityMethods.AppendLine(sourceCode);
                }
                #endregion

                #region { DeleteByForeignEntity }

                foreach (Association a in foreignAssociations)
                {
                    sourceCode = string.Format(Template.SqlProject.FunctionTemplate_DeleteByForeignEntity,
                                   t.DataType.Name,
                                   a.Type,
                                   a.ThisKey,
                                   GeneratorHelper.GenerateLocalVariableName(a.Type));

                    sbEntityMethods.AppendLine(sourceCode);
                }
                #endregion

                projectConfig.SetPropertyValue(
                            Template.SqlProject.CustomTag_EntityMethods,
                            GeneratorHelper.PlaceOpenClose(sbEntityMethods.ToString()));

                // Fill property in template
                sourceCode = GeneratorHelper.FillProperties(projectConfig, templateContent);

                // Write source code to file
                string generatedFilePath = string.Format("{0}\\{1}",
                            generatedDirectory.FullName,
                            string.Format(fileNameFormat, t.DataType.Name));

                GeneratorHelper.WriteToFile(generatedFilePath, sourceCode);
            }
        }

        private void GenerateCustomProviderFile(ProjectSetting projectConfig,
                                FileInfo templateFile,
                                DirectoryInfo generatedDirectory)
        {
            // Get entity mapper template file content
            string templateContent = GeneratorHelper.ReadFromFile(templateFile.FullName);

            // Mapper file name template
            string fileNameFormat = GeneratorHelper.GetFileNameFromat(templateFile.Name);


            // Fill methods to template
            foreach (Table t in Context.DatabaseSchema.Tables)
            {
                // Fill property in template
                string sourceCode = GeneratorHelper.FillProperties(projectConfig, templateContent);

                // Write source code to file
                string generatedFilePath = string.Format("{0}\\{1}",
                            generatedDirectory.FullName,
                            string.Format(fileNameFormat, t.DataType.Name));

                GeneratorHelper.WriteToFile(generatedFilePath, sourceCode);
            }
        }

        protected void GenerateStoredProcedureFile(ProjectSetting projectConfig,
                                FileInfo templateFile,
                                DirectoryInfo generatedDirectory)
        {
            // Get entity mapper template file content
            string templateContent = GeneratorHelper.ReadFromFile(templateFile.FullName);

            // Mapper file name template
            string fileNameFormat = GeneratorHelper.GetFileNameFromat(templateFile.Name);

            // Store methods
            StringBuilder sbStoredProcMethods = new StringBuilder();

            // Fill methods to template
            foreach (Function f in Context.DatabaseSchema.Functions)
            {
                GenerateStoreMethod(f, sbStoredProcMethods);
            }

            projectConfig.SetPropertyValue(
                            Template.SqlProject.CustomTag_StoredProcedureMethods,
                            GeneratorHelper.PlaceOpenClose(sbStoredProcMethods.ToString()));

            // Fill property in template
            string sourceCode = GeneratorHelper.FillProperties(projectConfig, templateContent);

            // Write source code to file
            string generatedFilePath = string.Format("{0}\\{1}",
                        generatedDirectory.FullName,
                        string.Format(fileNameFormat, string.Empty));

            GeneratorHelper.WriteToFile(generatedFilePath, sourceCode);
        }

        protected void GenerateCustomStoredProcedureFile(ProjectSetting projectConfig,
                                FileInfo templateFile,
                                DirectoryInfo generatedDirectory)
        {
            // Get entity mapper template file content
            string templateContent = GeneratorHelper.ReadFromFile(templateFile.FullName);

            // Mapper file name template
            string fileNameFormat = GeneratorHelper.GetFileNameFromat(templateFile.Name);

            // Fill property in template
            string sourceCode = GeneratorHelper.FillProperties(projectConfig, templateContent);

            // Write source code to file
            string generatedFilePath = string.Format("{0}\\{1}",
                        generatedDirectory.FullName,
                        string.Format(fileNameFormat, string.Empty));

            GeneratorHelper.WriteToFile(generatedFilePath, sourceCode, true);
        }

        private void GenerateStoreMethod(Function f, StringBuilder sbStoredProcMethods)
        {
            if (f == null)
            {
                return;
            }

            if (sbStoredProcMethods == null)
            {
                sbStoredProcMethods = new StringBuilder();
            }

            if (f.Return != null)
            {
                sbStoredProcMethods.AppendLine(string.Format(
                            Template.SqlProject.FunctionTemplate_StoredProcReturnInt,
                            f.Method,
                            GeneratorHelper.GetReturnType(Context, f),
                            BuildParameterDeclaration(f.Parameters),
                            BuildParameterCall(f.Parameters),
                            BuildGetParameterValues(f)));
            }
            else if (f.ElementType != null)
            {
                sbStoredProcMethods.AppendLine(string.Format(
                            Template.SqlProject.FunctionTemplate_StoredProcReturnType,
                            f.Method,
                            GeneratorHelper.GetElementType(Context, f),
                            BuildParameterDeclaration(f.Parameters),
                            BuildParameterCall(f.Parameters),
                            Template.SqlProject.CommandTemplate_ExecuteReturnSPTable,
                            BuildGetParameterValues(f)));
            }
        }

        private string BuildParameterDeclaration(List<Parameter> parameters)
        {
            StringBuilder sbParameters = new StringBuilder();

            foreach (Parameter p in parameters)
            {
                if (sbParameters.Length > 0)
                {
                    sbParameters.Append(", ");
                }

                sbParameters.AppendFormat(
                    Template.Global.Declaration,
                    (p.Direction == Template.Global.ParameterDirectionInOut) ? "ref" : string.Empty,
                    GeneratorHelper.GetNullableType(p.Type),
                    p.Param);
            }

            return sbParameters.ToString();
        }

        private string BuildParameterCall(List<Parameter> parameters)
        {
            if (parameters == null)
            {
                return string.Empty;
            }

            StringBuilder sbParameters = new StringBuilder();

            if (parameters.Count > 0)
            {
                sbParameters.Append(", null");
            }

            foreach (Parameter p in parameters)
            {
                sbParameters.Append(", ");
                sbParameters.Append(string.IsNullOrEmpty(p.Param) ? p.Name : p.Param);
            }

            return sbParameters.ToString();
        }

        private string BuildGetParameterValues(Function f)
        {
            StringBuilder sbParameters = new StringBuilder();

            foreach (Parameter p in f.Parameters)
            {
                if (p.Direction == Template.Global.ParameterDirectionInOut)
                {
                    sbParameters.AppendLine(string.Format(
                        Template.SqlProject.CommandTemplate_GetPropertyValue,
                        GeneratorHelper.GetStoredProcedureName(f.Name),
                        p.Name,
                        p.Param,
                        GeneratorHelper.GetNullableType(p.Type)));
                }
            }

            return sbParameters.ToString();
        }
    }
}